Let’s start by taking the term DevOps apart. It consists of Dev (development) and Ops (operations: the operation of software). It’s important to note that the players don’t have to be the same. In fact, it’s recommended that development and administration professionals focus together to ensure the approach’s maximum success. According to Wikipedia, “DevOps is intended to enable more effective and efficient collaboration between Dev, Ops, QA, and business departments through shared incentives, processes, and software tools.” [1] However, with GitLab, it is also possible to master the entire route from development to automated testing, to rollout, and to production alone as a front-end developer.
In the area of front-end applications, we can even easily map the entire path between development and operations. Thanks to the numerous frameworks and services, not much knowledge is required to create an SPA and continuously test and install it in a fully automated manner. The term installation is accurate in the sense that we have the software build by npm (Node Package Manager), for instance, and then we copy it to the web server like a package. The final package, which was created with a JavaScript framework, brings all dependencies with it and requires no additional special features on the server itself. In many cases, this makes it possible to host the application directly on a service like GitLab Pages [2]. As a frontend developer, we can work with our code repository, test, and roll out/install the software. This means that we don’t need any additional support to manage a project in its entirety. In the past, we referred to this as a classic one-man show. But today, we can call the approach DevOps.
What do we need for DevOps?
Creating a new project “from scratch”, on a greenfield site for instance, is becoming increasingly rare. But once we’re allowed to enjoy this comfort, we need to think of everything we need during the initial environment setup. What would that be?
- The code repository
- A framework for development
- A framework for testing the application
- Continuous Integration – automated application testing
- Continuous Delivery – automated application installation
The DevOps setup for the web
We use GitLab as our code repository. An infinite number of private repositories are available for private users, offering all the necessary functions for managing your code and creating a CI/CD pipeline [3]. React will be our application’s framework. This will make it fast and lightweight for JavaScript developers to create, test, and extend complex SPAs with a variety of available components and UI libraries. In this example, we’ll use the Material UI library for the UI.
The React Testing Library, which is well established in the community, is used as the testing framework. CI and CD are provided by GitLab. The vendor’s shared runners ensure that with every commit to our code repository, the appropriate rules are checked and steps in the pipeline are automatically executed. This can be automated testing or by installing the software in production.
iJS Newsletter
Keep up with JavaScript’s latest news!
Creating a GitLab repository
Later, the project will run on GitLab Pages. So we’ll create a new GitLab repository and directly create the YAML script with the name .gitlab-ci.yaml for the CI/CD pipeline (Listing 1).
image: node:latest stages: - build - test - deploy cache: paths: - node_modules/ install_dependencies: stage: build script: - npm install artifacts: paths: - node_modules/ testing_testing: stage: test script: npm test artifacts: paths: - ./ pages: stage: deploy script: - ./node_modules/.bin/react-scripts build - rm -rf public - mv build public artifacts: paths: - public only: - master
How do we read this file? At the top is the image that the server is based on, which is booted to run the pipeline. We want to compile a React application, so we need a node image. Subsequently, the stages are defined. We want to “build”, “test”, and “install” the application. So, we define build, test, and deploy. In the build step, the project is built just like the developer would in their local environment. Next, the created application is tested in the test step. The last step, if the first two steps were successful, is deploy. This publishes the /public directory created by React for GitLab Pages and makes it available on the web. We’ll see exactly how this works in the “Installation” section of this article.
GAIN INSIGHTS INTO THE DO'S & DON'TS
Angular Tips & Tricks
React setup
The basis for our React setup is the pipeline-app project. With this, we’ve already installed all the dependencies we need, such as the React Testing Library:
npx create-react-app pipeline-app
We also need a renderer for the DOM (Document Object Model, so HTML):
npm install react-test-renderer
To make this example a small working app later, we still need the router allowing linking subpages. We’ll also use the Material UI design system to visually enhance interaction elements:
npm install @material-ui/core npm install react-router-dom
How the React Testing Library works and how to use it
Basically, we want to test if our application elements are arranged correctly and respond according to its interaction. Detailed online documentation shows how to use the React Testing Library [4]. Together with Jest, we run the library locally for each change. The shortcut u for update will be important once you make significant changes to the DOM. This saves the new DOM as a snapshot in __snapshots__ and uses it as a comparison for further tests. The snapshot test can be created quickly and helps small applications that have only a few individual elements. But in most SPA projects, content is very dynamic, so we can rule out a complete DOM comparison. It’s recommended that you test individual elements here. Interactions on buttons are also helpful tests, for instance. The project linked contains a few examples.
Why do I think component testing is so important? In my experience, especially at the start of a new project, we quickly create a large number of components that we reuse in a wide variety of different places. This isn’t just about a simple button. Usually, a component includes a combination of an image, some text, and one or two click elements that are often needed more in the application. The props that are passed to a component provide any necessary individual behavior and, in the worst case, for a component’s changed appearance. This should make it clear why a DOM test is indispensable. Every small refactoring or extension to the component creates side effects that developers can no longer test by hand and can quickly lose track of. With the test, we will chisel the default behavior and default behavior appearance into stone. If the test fails, we have to think of another way to extend it, or better yet, duplicate the component. It’s true that in the spirit of clean code development it’s better not to do this—but not at any cost. Small deviations in appearance or behavior are difficult to handle in the abstract, especially for critical action elements. These days, when we have so many ready-made elements at our disposal, there’s no need to skimp on every line of code. The KISS principle (Keep It Simple, Stupid) is more important here, so that other developers can understand, maintain, and develop code without fear of big dependencies in huge projects. If we design our code to be lightweight and simple, then we have the added value of quick comprehensibility and its associated productivity during development.
Caution: In the CI/CD pipeline, the test stage must always run before the deploy stage. This is the purpose of continuous deployment. The app can only be rolled out to production when all programming tests and the application build runs smoothly.
Detecting failed tests
In GitLab, you can set emails for the CI/CD pipeline under settings/integrations in the Pipelines menu item. However, mail will only contain a link to the failed job. Then it displays a readable error so you can quickly reproduce it in the IDE. Of course, the same applies to the console in the IDE itself. Listing 2 shows an example of a failed test. In the excerpt shown, the text has been changed from Learn React to Learn2React.
36 App match snapshot 37 ✕ snapshot renders (14 ms) 38 App match snapshot › snapshot renders 39 expect(received).toMatchSnapshot() 40 Snapshot name: `App match snapshot snapshot renders 1` 41 - Snapshot - 1 42 + Received + 1 43 @@ -20,9 +20,9 @@ 44 className="App-link" 45 href="https://reactjs.org" 46 rel="noopener noreferrer" 47 target="_blank" 48 > 49 - Learn React 50 + Learn2React 51 </a> 52 </header> 53 </div> 54 14 | const component = renderer.create(<App />); 55 15 | let tree = component.toJSON(); 56 > 16 | expect(tree).toMatchSnapshot(); 57 | ^ 58 17 | }); 59 18 | }); 60 19 | 61 at Object.<anonymous> (src/__tests__/App.tests.js:16:18) 62 › 1 snapshot failed.
Installation (Continuous Delivery)
Let’s move on to the last step: installation in the production environment. The answer for this is GitLab Pages. The project is created and the finished compilation is copied into the /public directory, which Pages needs in order to find the application. Caution: For your deep links to work, you need to set the homepage parameter in the package.json. Additionally, you can also map your own domain. This works via the corresponding DNS record. First, you must verify yourself as the owner so that GitLab can confirm the usage and create a Let’s Encrypt certificate for you. A CNAME with the subdomain is also needed so that your domain manager can properly redirect the domain and GitLab can resolve it in turn.
GitLab for complex projects
Finally, a myth should be cleared up right now. In principle, running an SPA on GitLab for free is possible. But, it has a number of limitations. So, subdirectories like /about aren’t possible. Even .htaccess can’t be set, so the redirect to index.html, which should interpret the subdir, isn’t doable. A static page generator like GatsbyJS would create the corresponding files for all subpages and so it not only ensures even more performance when delivering the corresponding pages, but their general existence too. But in the example shown here, we go without this and solve the problem using hash routes. The directories look like this: /#/about. With this, we avoid the resolution of subdirectories and can implement optional routes.
Conclusion
Front-end developers are turning to DevOps faster than they might like. With the code repository’s capabilities, the entire setup can be put into place and run on its own. Of course, the setup can be extended as needed and transported to any server environment using Docker containers. In any case, the essence of the setup remains and is especially useful for teams. However, the core of the setup remains in any case and is especially useful for teams. As soon as we work on a project with several people, troubleshooting new bugs takes up a large amount of working time. Due to the test coverage in the frontend and continuous testing, broken pipelines are detected early and can be assigned to the originator (line of code) quickly.
The linked example project [5], [6] includes the entire basic setup for developing the shown SPA with React and running it on GitLab Pages. Even if the operations part of DevOps is daunting for some frontend developers, I think this setup can take away some of the fear and show the ease of administrative steps for operations. As I mentioned at the beginning, the DevOps approach is by no means designed for creating and operating projects by yourself. Different expertise in agile DevOps teams is essential for better, more secure software. But as this article showed, there’s also the possibility of a one-man show with DevOps.
I have also presented the procedure explained here in a video tutorial with further information [7].